package dom.xpath.query.xpath.expr;

import dom.xpath.query.EvalException;
import dom.xpath.query.xpath.XPContext;
import dom.xpath.query.xpath.values.NodeSet;
import dom.xpath.util.Array;
import dom.xpath.query.xpath.values.Item;

/**
 * FTNotExprs. This expresses the mild combination of ftand and ftnot.
 * The selection A not in B matches a token sequence that matches a, but
 * not when it is part of b.
 * 
 * @author Workgroup DBIS, University of Konstanz 2005-07, ISC License
 * @author Sebastian Gath
 */
public final class FTNotExprs extends ArrayExpr {
  /**
   * Constructor.
   * @param e operands joined with the mild not operator
   */
  public FTNotExprs(final Expression[] e) {
    exprs = e;
  }

  @Override
  public NodeSet eval(final XPContext ctx) throws EvalException {
    Item it = exprs[0].eval(ctx);
    if (it instanceof NodeSet) {
      int[][] tmp = ((NodeSet) it).ftidpos;
    
      if (tmp == null || tmp[0].length == 0) 
        return new NodeSet(new int[0], ctx);
    
      for (int i = 1; i < exprs.length; i++) {
        it = exprs[i].eval(ctx);
        if (it instanceof NodeSet) {
            tmp = determineNot(tmp, ((NodeSet) it).ftidpos);
        }
      }
      return new NodeSet(Array.extractIDsFromData(tmp), ctx, tmp);
    } 
    return null;  
  }

  @Override
  public Expression compile(final XPContext ctx) throws EvalException {
    final int el = exprs.length;
    for(int e = 0; e != el; e++) exprs[e] = exprs[e].compile(ctx);
    return this;
  }
  
  /**
   * Each result wordA, is not allowed to be contained in result wordB.
   * each result for wordA, that is not contained in result set wordB,
   * is added to returned result set.
   *
   * @param resultWordA result allowed to be contained
   * @param resultWordB result not allowed to be contained
   * @return data []
   */
  public static int[][] determineNot(final int[][] resultWordA,
      final int[][] resultWordB) {
    if((resultWordA == null && resultWordB == null) || resultWordA == null) {
      return null;
    }

    // all resultWordA are hits
    if(resultWordB == null) {
      return resultWordA;
    }

    // pointer on resultWordA
    int i = 0;
    // pointer on resultWordB
    int k = 0;
    // counter for result set
    int count = 0;
    // array for result set
    int[][] maxResult = new int[2][resultWordA[0].length];
    for(; i < resultWordA[0].length; i++) {
      // ignore all minor values
      while(k < resultWordB[0].length &&
          resultWordA[0][i] > resultWordB[0][k]) k++;

      // all done for resultWordB
      if(k == resultWordB[0].length) break;

      // same elements -> duplicated elements are possible (in both arrays)
      if(resultWordA[0][i] == resultWordB[0][k]) {
        // ignore following same elements in resultWordA
        while(i < resultWordA[0].length - 1 &&
            resultWordA[0][i] == resultWordA[0][i + 1]) i++;

        // ignore following same elements in resultWordA
        while(k < resultWordB[0].length - 1 &&
            resultWordB[0][k] == resultWordB[0][k + 1]) k++;

        // pointer on next element
        k++;
      } else {
        // apply result
        maxResult[0][count] = resultWordA[0][i];
        maxResult[1][count] = resultWordA[1][i];
        count++;
      }
    }

    int[][] result;
    // all done for resultWordA
    if(i == resultWordA[0].length) {
      if(count == 0) return null;

      // copy only filled cells
      result = new int[2][count];
      System.arraycopy(maxResult[0], 0, result[0], 0, count);
      System.arraycopy(maxResult[1], 0, result[1], 0, count);
    } else {
      result = new int[2][count + resultWordA[0].length - i];
      // copy only filled cells
      System.arraycopy(maxResult[0], 0, result[0], 0, count);
      System.arraycopy(maxResult[1], 0, result[1], 0, count);
      // copy not processed
      System.arraycopy(resultWordA[0], i, result[0], count,
          resultWordA[0].length - i);
      System.arraycopy(resultWordA[1], i, result[1], count,
          resultWordA[0].length - i);
    }
    return result;
  }
}
